//-------------------------------------------------------------------
//	Copyright (c) 2010-2012 Zhirnov Andrey
//	This file is part of the "UXGEN" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#include "SoundLoader.h"

namespace Engine
{
namespace Loaders
{

	bool DecodeDataPCM8(const binary_buffer_t &aData, binary_buffer_t &aResult)
	{
		aResult.Resize( aData.Size(), false );

		int8	*data = (int8 *)aData.ptr();
		int8	*buf  = (int8 *)aResult.ptr();
	
		for (uint i = 0; i < aData.Size(); ++i)
		{
			buf[i] = data[i] + int8(128);
		}
		return true;
	}


	bool DecodeDataPCM16(const binary_buffer_t &aData, binary_buffer_t &aResult)
	{
		aResult.Resize( aData.Size(), false );

		int16	*data = (int16 *)aData.ptr();
		int16	*buf  = (int16 *)aResult.ptr();
		uint	l = (uint)( aData.Size() / 2 );

		for (uint i = 0; i < l; ++i)
		{
			const int16		x = data[i];
			buf[i] = ((x << 8) & 0xFF00) | ((x >> 8) & 0x00FF);
		}
		return true;
	}


	int16 mulaw2linear(uint8 mulawbyte)
	{
		static const int16 exp_lut[8] = { 0, 132, 396, 924, 1980, 4092, 8316, 16764 };
	
		int16	sign,
				exponent,
				mantissa,
				sample;

		mulawbyte = ~mulawbyte;
		sign = (mulawbyte & 0x80);
		exponent = (mulawbyte >> 4) & 0x07;
		mantissa = mulawbyte & 0x0F;
		sample = exp_lut[exponent] + (mantissa << (exponent + 3));
	
		if (sign != 0)
		{
			sample = -sample;
		}
		return sample;
	}


	bool DecodeDataULaw(const binary_buffer_t &aData, binary_buffer_t &aResult)
	{
		aResult.Resize( aData.Size() * 2, false );

		uint8	*data = (uint8 *)aData.ptr();
		int16	*buf = (int16 *)aResult.ptr();
	
		for (uint i = 0; i < aData.Size(); ++i)
		{
			buf[i] = mulaw2linear( data[i] );
		}
		return true;
	}


	#define SIGN_BIT	(0x80)		// Sign bit for a A-law byte.
	#define QUANT_MASK	(0xf)		// Quantization field mask.
	#define SEG_SHIFT	(4)			// Left shift for segment number.
	#define SEG_MASK	(0x70)		// Segment field mask.

	int16 alaw2linear (uint8 a_val)
	{
		int16	t,
				seg;
		a_val ^= 0x55;
		t = (a_val & QUANT_MASK) << 4;
		seg = ((int16) a_val & SEG_MASK) >> SEG_SHIFT;
	
		switch (seg)
		{
			case 0:
				t += 8;
				break;
			case 1:
				t += 0x108;
				break;
			default:
				t += 0x108;
				t <<= seg - 1;
		}
		return (a_val & SIGN_BIT) ? t : -t;
	}

	#undef SIGN_BIT
	#undef QUANT_MASK
	#undef SEG_SHIFT
	#undef SEG_MASK


	bool DecodeDataALaw(const binary_buffer_t &aData, binary_buffer_t &aResult)
	{
		aResult.Resize( aData.Size() * 2, false );

		uint8	*data = (uint8 *)aData.ptr();
		int16	*buf  = (int16 *)aResult.ptr();

		for (uint i = 0; i < aData.Size(); ++i)
		{
			buf[i] = alaw2linear( data[i] );
		}
		return true;
	}

	
	bool DecodeData(const binary_buffer_t &aData, e_sound_format::type eFormat, binary_buffer_t &aResult)
	{
		switch ( eFormat )
		{
			case e_sound_format::PCM_LINEAR :
				aResult = aData;
				return true;

			case e_sound_format::PCM_8 :
				return DecodeDataPCM8( aData, aResult );

			case e_sound_format::PCM_16 :
				return DecodeDataPCM16( aData, aResult );

			case e_sound_format::ULAW_8 :
				return DecodeDataULaw( aData, aResult );

			case e_sound_format::ALAW_8 :
				return DecodeDataALaw( aData, aResult );

			default :
				return false;
		}
	}


	bool CodeDataPCM8(const binary_buffer_t &aData, const uint32_array_t &aSizes,
					  binary_buffer_t &aResultBuf, uint32_array_t &aResultSizes)
	{
		aResultSizes = aSizes;
		aResultBuf.Resize( aData.Size(), false );

		int8 const	*data = (int8 const *)aData.ptr();
		int8		*buf  = (int8 *)aResultBuf.ptr();
	
		for (uint i = 0; i < aData.Size(); ++i)
		{
			buf[i] = data[i] - (int8) 128;
		}
		return true;
	}


	bool CodeDataPCM16(const binary_buffer_t &aData, const uint32_array_t &aSizes,
					   binary_buffer_t &aResultBuf, uint32_array_t &aResultSizes)
	{
		aResultSizes = aSizes;
		aResultBuf.Resize( aData.Size(), false );

		int16 const	*data = (int16 const *)aData.ptr();
		int16		*buf  = (int16 *)aResultBuf.ptr();
		uint		l = (uint)( aData.Size() / 2 );

		for (uint i = 0; i < l; ++i)
		{
			int16	x = data[i];
			buf[i] = ((x << 8) & 0xFF00) | ((x >> 8) & 0x00FF);
		}
		return true;
	}

	
	bool CodeData(const binary_buffer_t &aData, e_sound_format::type eFormat, binary_buffer_t &aResultBuf)
	{
		uint32_array_t	a_sizes,
						a_res_sizes;

		a_sizes.PushBack( uint( aData.Size() ) );

		return CodeData( aData, a_sizes, eFormat, aResultBuf, a_res_sizes );
	}


	bool CodeData(const binary_buffer_t &aData, const uint32_array_t &aSizes, e_sound_format::type eFormat,
				  binary_buffer_t &aResultBuf, uint32_array_t &aResultSizes)
	{
		switch ( eFormat )
		{
			case e_sound_format::PCM_LINEAR :
				aResultBuf   = aData;
				aResultSizes = aSizes;
				return true;

			case e_sound_format::PCM_8 :
				return CodeDataPCM8( aData, aSizes, aResultBuf, aResultSizes );

			case e_sound_format::PCM_16 :
				return CodeDataPCM16( aData, aSizes, aResultBuf, aResultSizes );

			case e_sound_format::ULAW_8 :
				//return CodeDataULaw( aData, aSizes, aResultBuf, aResultSizes );

			case e_sound_format::ALAW_8 :
				//return CodeDataALaw( aData, aSizes, aResultBuf, aResultSizes );

			default :
				return false;
		}
	}
	

	bool _LoadSound(IFilePtr pFile, TSoundSource &sSound)
	{
		bool	b_read = true;
		char	a_file_header[12];

		b_read &= pFile->ReadS( a_file_header, 12 );
		a_file_header[11] = '\0';

		CHECK_VALUE( strcmp( a_file_header, s_aSndFileHeader ) == 0 );

		TFileHeader		s_fheader;
		TSoundHeader	s_header;

		b_read &= pFile->ReadS( &s_fheader, sizeof(s_fheader) );

		CHECK_VALUE( s_fheader.uHeaderSize == sizeof(s_header) );

		b_read &= pFile->ReadS( &s_header, sizeof(s_header) );
		b_read &= pFile->SeekS( s_fheader.uUserDataSize, e_seek::CURRENT );

		uint32_array_t	a_block_sizes( s_header.uBlocksCount );

		b_read &= pFile->ReadS( a_block_sizes.ptr(), (uint)a_block_sizes.Size() );

		uint	u_size = 0;

		for (uint i = 0; i < a_block_sizes.Count(); ++i)
			u_size += a_block_sizes[i];

		binary_buffer_t	a_data;
		a_data.Resize( u_size, false);

		b_read &= pFile->ReadS( a_data.ptr(), (uint)a_data.Size() );
		b_read &= DecodeData( a_data, s_header.eSndFormat, sSound.aData );

		sSound.eIntFormat	= s_header.eSndFormat;
		sSound.eFormat		= (al_format::type)s_header.uAlFormat;
		sSound.uFreq		= s_header.uFreq;
		sSound.bLooping		= s_header.bLooping;
		sSound.fGain		= s_header.fGain;
		sSound.fPitch		= s_header.fPitch;

		return b_read;
	}


	bool LoadSound(IFilePtr pFile, TSoundSource &sSound)
	{
		CHECK_ARGUMENT( pFile != nullptr );

		bool	ret = _LoadSound( pFile, sSound );
		
		CHECK_ERROR( ret, "Can't read sound file data \"" << pFile->GetName() << '"' );
		return true;
	}


	bool _LoadStreamedSound(IFilePtr pFile, TStreamedSound &sSound)
	{
		char	a_file_header[12];
		bool	b_read = true;

		b_read &= pFile->ReadS( a_file_header, 12 );
		a_file_header[11] = '\0';

		CHECK_VALUE( strcmp( a_file_header, s_aSndFileHeader ) == 0 );

		TFileHeader		s_fheader;
		TSoundHeader	s_header;
		uint32_array_t	a_block_sizes;

		b_read &= pFile->ReadS( &s_fheader, sizeof(s_fheader) );

		CHECK_VALUE( s_fheader.uHeaderSize == sizeof(s_header) );

		b_read &= pFile->ReadS( &s_header, sizeof(s_header) );
		a_block_sizes.Resize( s_header.uBlocksCount, false );
		b_read &= pFile->ReadS( a_block_sizes.ptr(), (uint)a_block_sizes.Size() );

		sSound.eFormat		= (al_format::type)s_header.uAlFormat;
		sSound.eIntFormat	= s_header.eSndFormat;
		sSound.uFreq		= s_header.uFreq;
		sSound.bLooping		= s_header.bLooping;
		sSound.fGain		= s_header.fGain;
		sSound.fPitch		= s_header.fPitch;
		sSound.pFile		= pFile;

		uint	u_pos = pFile->GetPos();

		sSound.aBufSizes.Resize( a_block_sizes.Count(), false );

		for (uint i = 0; i < a_block_sizes.Count(); ++i)
		{
			sSound.aBufSizes[i].uOffset	= u_pos;
			sSound.aBufSizes[i].uSize	= a_block_sizes[i];
			u_pos += a_block_sizes[i];
		}

		return b_read;
	}
	

	bool LoadStreamedSound(IFilePtr pFile, TStreamedSound &sSound)
	{
		CHECK_ARGUMENT( pFile != nullptr );

		bool	ret = _LoadStreamedSound( pFile, sSound );
		
		CHECK_ERROR( ret, "Can't load streamed sound \"" << pFile->GetName() << '"' );
		return true;
	}


	bool _SaveSound(IFilePtr pFile, const TStreamedSource &sSound)
	{
		TFileHeader		s_fheader;
		TSoundHeader	s_header;
		bool			b_write = true;
		uint			u_temp = 0;

		binary_buffer_t	a_data;
		uint32_array_t	a_sizes;

		s_fheader.uHeaderSize	= sizeof(s_header);
		s_fheader.uUserDataSize	= 0;

		s_header.eSndFormat		= sSound.eIntFormat;
		s_header.uAlFormat		= sSound.eFormat;
		s_header.uFreq			= sSound.uFreq;
		s_header.bLooping		= sSound.bLooping;
		s_header.fGain			= sSound.fGain;
		s_header.fPitch			= sSound.fPitch;
		s_header.uBlocksCount	= (uint)sSound.aSizes.Count();

		b_write &= pFile->WriteS( s_aSndFileHeader, 12 );
		b_write &= pFile->WriteS( &s_fheader, sizeof(s_fheader) );
		b_write &= pFile->WriteS( &s_header, sizeof(s_header) );

		CHECK_RETURN( CodeData( sSound.aData, sSound.aSizes, sSound.eIntFormat, a_data, a_sizes ) );

		b_write &= pFile->WriteS( a_sizes.ptr(), (uint)a_sizes.Size() );
		b_write &= pFile->WriteS( a_data.ptr(),  (uint)a_data.Size()  );

		return b_write;
	}
	

	bool SaveSound(IFilePtr pFile, const TSoundSource &sSound)
	{
		CHECK_ARGUMENT( pFile != nullptr );

		TStreamedSource	s_sound;

		s_sound.aSizes.PushBack( uint( sSound.aData.Size() ) );
		s_sound.aData		= sSound.aData;
		s_sound.bLooping	= sSound.bLooping;
		s_sound.eFormat		= sSound.eFormat;
		s_sound.eIntFormat	= sSound.eIntFormat;
		s_sound.fGain		= sSound.fGain;
		s_sound.fPitch		= sSound.fPitch;
		s_sound.uFreq		= sSound.uFreq;

		bool	ret = _SaveSound( pFile, s_sound );
		
		CHECK_ERROR( ret, "Can't save sound \"" << pFile->GetName() << '"' );
		return true;
	}


	bool SaveSound(IFilePtr pFile, const TStreamedSource &sSound)
	{
		CHECK_ARGUMENT( pFile != nullptr );
		
		bool	ret = _SaveSound( pFile, sSound );
		
		CHECK_ERROR( ret, "Can't save sound \"" << pFile->GetName() << '"' );
		return true;
	}

//-------------------------------------------------------------------

}	// Loaders
}	// Engine